class Game {
    constructor(scenes) {
        this.scenes = scenes
        this.listeners = []
        this.pokerList = []
        this.incompleteStackList = []
        this.completedStackList = []
        this.randomStack = new PokerStack(this.scenes.randomStackElement)
        this.position = null
        this.draggable = false
        this.selectedPoker = null
        this.selectedPokers = []
        this.selectedStack = null
        this.randomOpenedStack = new PokerStack(this.scenes.randomOpenStackElement)
        this.init()
        this.allStackList = [...this.completedStackList, ...this.incompleteStackList, this.randomOpenedStack, this.randomStack]
        this.tipsOrder = 0
    }
    init() {
        this.initPokerList()
        this.positionRandomStackChildren()
        this.initIncompleteStackList()
        this.initCompletedStackList()
        this.positionIncompleteStacksChildren()
        this.shuffle()
        this.dealPokers()
        this.bindEvents()
    }

    initPokerList() {
        let suits = ['spade', 'heart', 'club', 'diamond']
        for (let i = 0; i < this.scenes.pokersElements.length; i++) {
            let suitNumber = Math.floor(i / 13)
            let suit = suits[suitNumber]
            let number = (i % 13) + 1
            let poker = new Poker(number, suit, this.scenes.pokersElements[i])
            poker.el.dataset.src = `image/${suit}_${number}.png`
            this.pokerList.push(poker)
            this.randomStack.push(poker)
        }
    }
    initIncompleteStackList() {
        let { incompleteStackElements } = this.scenes
        for (let i = 0; i < incompleteStackElements.length; i++) {
            let stack = incompleteStackElements[i]
            let pokerStack = new PokerStack(stack)
            this.incompleteStackList.push(pokerStack)
        }
    }
    initCompletedStackList() {
        let { completedStackElements } = this.scenes
        for (let i = 0; i < completedStackElements.length; i++) {
            let stack = completedStackElements[i]
            let pokerStack = new PokerStack(stack)
            this.completedStackList.push(pokerStack)
        }
    }
    positionIncompleteStackChildren(pokerStack, delay = false) {
        if (delay) {
            this.positionStackChildrenDelay(pokerStack, 0, 10, 'open', 20)
        } else {
            this.positionStackChildren(pokerStack, 0, 10, 'open', 20)
        }
    }

    positionIncompleteStacksChildren(delay = false) {
        for (let i = 0; i < this.incompleteStackList.length; i++) {
            let stack = this.incompleteStackList[i]
            this.positionIncompleteStackChildren(stack, delay)
        }
    }
    positionRandomStackChildren() {
        this.positionStackChildren(this.randomStack)
    }
    positionCompletedStacksChildren() {
        for (let i = 0; i < this.completedStackList.length; i++) {
            let stack = this.completedStackList[i]
            this.positionStackChildren(stack)
        }
    }
    positionRandomOpenedStack() {
        this.positionStackChildren(this.randomOpenedStack)
    }
    positionAllStackChildren() {
        this.positionRandomStackChildren()
        this.positionCompletedStacksChildren()
        this.positionIncompleteStacksChildren()
        this.positionRandomOpenedStack()
    }
    positionStackChildren(pokerStack, offsetX = 0, offsetY = 0, className = undefined, offsetY1 = 20) {
        let children = pokerStack.el.children
        for (let i = 0; i < children.length; i++) {
            let child = children[i]
            child.style.left = pokerStack.left + offsetX + 'px'
            let pre = child.previousElementSibling
            if (className !== undefined && child.classList.contains(className) && pre !== null) {
                child.style.top = pre.offsetTop + offsetY1 + 'px'
            } else {
                child.style.top = pokerStack.top + i * offsetY + 'px'
            }
        }
    }

    positionPoker(poker) {
        poker.el.style.left = poker.currentStack.left
        let preSibling = poker.el.previousElementSibling
        if (preSibling === null) {
            poker.el.style.top = poker.currentStack.top + 'px'
        } else if (poker.el.classList.contains('open')) {
            poker.el.style.top = preSibling.offsetTop + 20 + 'px'
        } else {
            poker.el.style.top = preSibling.offsetTop + 10 + 'px'
        }
    }

    positionStackChildrenDelay(pokerStack, offsetX = 0, offsetY = 0, className = undefined, offsetY1 = 20) {
        let children = pokerStack.el.children
        for (let i = 0; i < children.length; i++) {
            setTimeout(() => {
                let child = children[i]
                child.style.left = pokerStack.left + offsetX + 'px'
                let pre = child.previousElementSibling
                if (className !== undefined && child.classList.contains(className) && pre !== null) {
                    child.style.top = pre.offsetTop + offsetY1 + 'px'
                } else {
                    child.style.top = pokerStack.top + i * offsetY + 'px'
                }
                if (i === children.length - 1) {
                    this.blurAll()
                }
            }, 50)

        }
    }

    shuffle() {
        let array = this.randomStack.pokerList
        let currentIndex = array.length, temporaryValue, randomIndex;
        while (0 !== currentIndex) {
            randomIndex = Math.floor(Math.random() * currentIndex);
            currentIndex -= 1;
            temporaryValue = array[currentIndex];
            array[currentIndex] = array[randomIndex];
            array[randomIndex] = temporaryValue;
        }
        return array;
    }
    initPokersPosition(targetStack, poker) {
        targetStack.push(poker)
        poker.smooth()
        this.positionIncompleteStackChildren(targetStack, true)
    }
    findStack(element) {
        for (let i = 0; i < this.allStackList.length; i++) {
            let stack = this.allStackList[i]
            if (stack.findPoker(element) !== null) {
                return stack
            }
        }
        return null
    }
    selectPokers(element) {
        if (this.selectedStack  && this.selectedPoker ) {
            this.selectedStack.blurPokers(this.selectedPoker)
        }
        let stack = this.findStack(element)
        this.selectedStack = stack
        this.selectedPoker = stack.findPoker(element)
        this.selectedPokers = stack.nextSiblingPokers(this.selectedPoker)
        stack.highLightPokers(this.selectedPoker)
    }

    insertToCompletedStack(stack, poker) {
        if (stack !== null && stack.canPushStrict(poker)) {
            stack.push(poker)
            poker.smooth()
            poker.highLight()
            this.positionStackChildrenDelay(stack)
        }
    }
    insertPokers({clientX, clientY}) {
        let stack = this.closestCompletedStack(clientX, clientY)
        let stack1 = this.closestIncompleteStack(clientX)
        if (stack !== null && stack.canPushStrict(this.selectedPoker) && this.selectedPokers.length === 1) {
            stack.push(...this.selectedPokers)
            this.openLastPoker()
            this.positionAllStackChildren()
            stack.blurPokers(this.selectedPoker)
            this.resetTipsOrder()
        } else if (stack1 !== null && stack1.canPush(this.selectedPoker)) {
            stack1.push(...this.selectedPokers)
            this.openLastPoker()
            stack1.blurPokers(this.selectedPoker)
            this.positionAllStackChildren()
            this.resetTipsOrder()
        } else {
            this.positionAllStackChildren()
        }
    }
    movePokers({ clientX: x, clientY: y}) {
        let deltaX = x - this.position[0]
        let deltaY = y - this.position[1]
        let list = this.selectedPokers
        for (let i = 0; i < list.length; i++) {
            let p = list[i]
            let el = p.el
            el.style.top = parseInt(el.style.top, 10) + deltaY + 'px'
            el.style.left = parseInt(el.style.left, 10) + deltaX + 'px'
        }
        this.position = [x, y]
    }
    popPokersFromRandomStack(callback) {
        let length = this.incompleteStackList.length
        let index = 0
        let timer = setInterval(() => {
            if (index < length) {
                let pokers = this.randomStack.multiPop(length - index)
                callback.call(this, pokers)
                index += 1
            } else  {
                setTimeout(() => {
                    this.openLastPoker()
                    this.positionAllStackChildren()
                }, 200)
                clearInterval(timer)
            }
        }, 100)
    }
    travelStacksList(pokers) {
        let length1 = this.incompleteStackList.length
        let length2 = pokers.length
        let index1 = length1 - length2
        let index2 = 0
        while (index2 < length2) {
            let stack = this.incompleteStackList[index1]
            let poker = pokers[index2]
            this.initPokersPosition(stack, poker)
            index2 += 1
            index1 += 1
        }
    }
    dealPokers() {
        this.popPokersFromRandomStack(this.travelStacksList)
        // let length = this.incompleteStackList.length
        // for (let i = length; i > 0; i--) {
        //     setTimeout(() => {
        //         let pokers = this.randomStack.multiPop(i)
        //         for (let j = length - i; j < length; j++) {
        //             let stack = this.incompleteStackList[j]
        //             let index = length - j - 1
        //             let p = pokers[index]
        //             setTimeout(() => {
        //                 this.initPokersPosition(stack, p)
        //                 if (i === 1 && j === 6) {
        //                     setTimeout(() => {
        //                         this.openLastPoker()
        //                         this.positionIncompleteStacksChildren()
        //                     }, 300)
        //                  }
        //             }, j * 100)
        //         }
        //     }, (length - i + 1) * 100)
        // }
    }

    openLastPoker() {
        for (let i = 0; i < this.incompleteStackList.length; i++) {
            let stack = this.incompleteStackList[i]
            let lastPoker = stack.lastPoker()
            if (lastPoker !== null) {
                lastPoker.open()
            }
        }
        this.finishGame()

    }
    closestIncompleteStack(clientX) {
        let left = this.incompleteStackList[0].left - 10
        if (clientX < left || clientX > left + 710) {
            return null
        }
        let n = Math.floor((clientX - left) / 100)
        return this.incompleteStackList[n]
    }
    closestCompletedStack(clientX, clientY) {
        let left = this.completedStackList[0].left - 10
        let top = this.completedStackList[0].top - 20
        let XYisValid = clientX >= left && clientX <= left + 410 && clientY > top && clientY <= top + 150
        if (XYisValid) {
            let n = Math.floor((clientX - left) / 100)
            return this.completedStackList[n]
        } else {
            return null
        }
    }
    blurAll() {
        for (let i = 0; i < this.pokerList.length; i++) {
            let p = this.pokerList[i]
            p.blur()
        }
    }

    getRandomOpenedStackLastPoker() {
        return this.randomOpenedStack.lastPoker()
    }

    getIncompleteStackFirstOpenedPokers() {
        let list = this.incompleteStackList
        let result = []
        for (let i = 0; i < list.length; i++) {
            let p = list[i].firstOpenedPoker()
            if (p !== null) {
                result.push(p)
            }
        }
        return result
    }
    getIncompleteStackLastPokers() {
        let list = this.incompleteStackList
        let result = []
        for (let i = 0; i < list.length; i++) {
            let p = list[i].lastPoker()
            if (p !== null) {
                result.push(p)
            }
        }
        return result
    }
    compareRandomOpenedWithIncomplete() {
        let p = this.getRandomOpenedStackLastPoker()
        if (p === null) {
            return []
        }
        let result = []
        for (let i = 0; i < this.incompleteStackList.length; i++) {
            let stack = this.incompleteStackList[i]
            if (stack.canPush(p)) {
                result.push([p, stack.lastPoker() || stack])
            }
        }
        return result
    }
    compareRandomOpenedWithCompleted() {
        let p = this.getRandomOpenedStackLastPoker()
        if (p === null) {
            return []
        }
        let result = []
        for (let i = 0; i < this.completedStackList.length; i++) {
            let stack = this.completedStackList[i]
            if (stack.canPushStrict(p)) {
                result.push([p, stack.lastPoker() || stack])
            }
        }
        return result
    }
    compareIncompleteWithCompleted() {
        let lastPokersIn = this.getIncompleteStackLastPokers()
        let result = []
        for (let i = 0; i < lastPokersIn.length; i++) {
            for (let j = 0; j < this.completedStackList.length; j++) {
                let stack = this.completedStackList[j]
                let lp = lastPokersIn[i]
                if (stack.canPushStrict(lp)) {
                    result.push([lp, stack.lastPoker() || stack])
                }
            }
        }
        return result
    }
    compareIncompleteStackSelf() {
        let firstOpenedPokers = this.getIncompleteStackFirstOpenedPokers()
        let result = []
        for (let i = 0; i < firstOpenedPokers.length; i++) {
            for (let j = 0; j < this.incompleteStackList.length; j++) {
                let stack = this.incompleteStackList[j]
                let fop = firstOpenedPokers[i]
                if (stack.canPush(fop)) {
                    result.push([fop, stack.lastPoker() || stack])
                }
            }
        }
        return result
    }
    getOpenPokers() {
        let list = []
        for (let i = 0; i < this.pokerList.length; i++) {
            let p = this.pokerList[i]
            if (p.opened === true) {
                list.push(p)
            }
        }
        return list
    }
    finishGame() {
        let openPokers = this.getOpenPokers()
        if (openPokers.length === 52 && this.randomOpenedStack.length <= 1) {
            let lastPokers = this.getIncompleteStackLastPokers()
            let rl = this.getRandomOpenedStackLastPoker()
            if (rl !== null) {
                lastPokers.push(rl)
            }
            let timer = 0
            if (lastPokers.length === 0) {
                clearTimeout(timer)
                this.blurAll()
                setTimeout(() => {
                    this.youWin()
                }, 200)
            } else {
                for (let i = 0; i < lastPokers.length; i++) {
                    timer = setTimeout(() => {
                        for (let j = 0; j < this.completedStackList.length; j++) {
                            let stack = this.completedStackList[j]
                            let poker = lastPokers[i]
                            this.insertToCompletedStack(stack, poker)
                        }
                        if (i === lastPokers.length - 1) {
                            this.finishGame()
                        }
                    }, 200 * (i + 1))
                }
            }
        }
    }
    filterCompareResult(arr) {
        for (let i = 0; i < arr.length; i++) {
            let poker = arr[i][0]
            if (poker.number === 13 && poker.currentStack.pokerList[0] === poker) {
                arr.splice(i, 1)
                i -= 1
            }
        }
        return arr
    }
    findCanBeInsertedPoker() {
        let r1 = this.compareIncompleteWithCompleted()
        let r2 = this.compareRandomOpenedWithCompleted()
        let r3 = this.compareIncompleteStackSelf()
        let r4 = this.compareRandomOpenedWithIncomplete()
        let result = [...r1, ...r2, ...r3, ...r4]
        return this.filterCompareResult(result)
    }
    giveTips() {
        let pokerCoupleList = this.findCanBeInsertedPoker()
        let len = pokerCoupleList.length
        if (len === 0) {
            this.randomStack.twinkle()
            return
        }
        this.tipsOrder = this.tipsOrder % len
        for (let i = 0; i < len; i++) {
            let p = pokerCoupleList[i]
            p[0].removeTwinkle()
            p[1].removeTwinkle()
        }
        let pokerCouple = pokerCoupleList[this.tipsOrder]
        pokerCouple[0].twinkle()
        pokerCouple[1].twinkle()
        this.tipsOrder += 1
    }
    resetTipsOrder() {
        this.tipsOrder = 0
    }
    canWin() {
        for (let i = 0; i < this.incompleteStackList.length; i++) {
            let stack = this.incompleteStackList[i]
            if (stack.canWin() === false) {
                return false
            }
        }
        return true
    }
    youWin() {
        let youWinDiv = e('#id-div-you-win')
        youWinDiv.classList.add('show')
    }
    restart() {
        this.scenes.el.remove()
        let body = e('body')
        let t = `<div id="id-div-solitaire-container"></div>`
        appendHtml(body, t)
        let el = e('#id-div-solitaire-container')
        this.removeEvents()
        let scenes = new Scenes(el)
        new Game(scenes)
    }
    bindEventMousedown() {
        let listener = (event) => {
            let self = event.target
            if (self.classList.contains('open')) {
                this.selectPokers(self)
                this.position = [event.clientX, event.clientY]
                this.draggable = true
            }
        }
        log('this.listeners', this.listeners)
        this.listeners.push([this.scenes.el, 'mousedown', listener])
        this.scenes.el.addEventListener('mousedown', listener)
    }

    bindEventMousemove() {
        let listener = (event) => {
            if(this.draggable === false){
                return
            }
            this.movePokers(event)
        }
        document.addEventListener('mousemove', listener)
        this.listeners.push([document, 'mousemove', listener])
    }
    bindEventMouseup() {
        let listener = (event) => {
            let self = event.target
            if (self.classList.contains('open')) {
                this.insertPokers(event)
            }
            this.draggable = false
        }
        document.addEventListener('mouseup', listener)
        this.listeners.push([document, 'mouseup', listener])

    }
    bindEventRandomStackClick() {
        let listener = () => {
            let length = this.randomStack.length
            if (length !== 0) {
                this.randomStack.setBoxShadow()
                let poker = this.randomStack.pop()
                this.randomOpenedStack.push(poker)
                poker.open()
                this.finishGame()
            } else {
                let pokers = this.randomOpenedStack.splice(0)
                pokers.reverse()
                this.randomStack.push(...pokers)
                this.randomStack.coverAllChildrenPokers()
                this.randomStack.setBoxShadow()
            }
            this.blurAll()
            this.resetTipsOrder()
            this.positionAllStackChildren()
        }
        this.randomStack.el.addEventListener('click', listener)
        this.listeners.push([this.randomStack.el, 'click', listener])
    }
    bindEventClick() {
        let listener = (event) => {
            let self = event.target
            let stack = this.findStack(self)
            if (stack !== this.selectedStack) {
                log('stack', stack)
                this.blurAll()
            }
        }
        document.addEventListener('click', listener)
        this.listeners.push([document, 'click', listener])
    }
    bindEventTips() {
        let button = find(this.scenes.el, '#id-div-tips')
        button.addEventListener('click', (event) => {
            this.giveTips(this.tipsOrder)
        })
    }
    bindEventRestart() {
        let listener = event => {
            let self = event.target
            if (self.id === 'id-div-restart') {
                this.restart()
            }
        }
        this.scenes.el.addEventListener('click', listener)
        this.listeners.push([this.scenes.el, 'click', listener])
    }
    bindEventBgm() {
        let a = e('audio')
        let toggle = e('#id-div-bgm')
        let playing = false
        document.addEventListener('click', (event) => {
            let self = event.target
            if (self.id === toggle.id) {
                if (playing === false) {
                    a.play()
                    toggle.classList.add('pause')
                    playing = true
                } else {
                    a.pause()
                    toggle.classList.remove('pause')
                    playing = false
                }
            }
        })
    }
    bindEvents() {
        this.bindEventMousedown()
        this.bindEventMousemove()
        this.bindEventMouseup()
        this.bindEventRandomStackClick()
        this.bindEventClick()
        this.bindEventTips()
        this.bindEventRestart()
        this.bindEventBgm()
    }
    removeEvents() {
        for (let i = 0; i < this.listeners.length; i++) {
            let l = this.listeners[i]
            l[0].removeEventListener(l[1], l[2])
        }
    }
}
const __main = () => {
    let container = e('#id-div-solitaire-container')
    let scenes = new Scenes(container)
    let game = new Game(scenes)
}
__main()



